package contegor.scire.core;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicInteger;

import contegor.scire.core.Link.Flow;
import contegor.scire.core.modifications.Modification;
import contegor.scire.core.queries.Query;


/**
 * <code>{@link Container}</code> implementation. All the {@link Connectable}s it holds
 * will be {@link ConnectableImpl}s. All {@link Link}s {@link LinkImpl}s.
 * 
 * All modifications will occur under a transaction. The policy that govern this transactions is:
 * - Any {@link Connectable} created, updated or deleted by a transaction is hold under its control until
 *   it is committed or a roll-back is done.
 * - A transaction can not modify a {@link Connectable} under other transaction control.
 * - Deletes are done through a 'marked as deleted' mechanism until they are committed. Then is when
 *   the affected {@link Connectable}s are effectively erased.
 * - If inside a transaction modification is undone, it will be as if that modification was never done.
 *   If a {@link Connectable} is created, and then, the same transaction deletes it, it will be erased, not
 *   'marked as deleted'. If a {@link Connectable} is deleted, and then that operation is undone,
 *   the 'deleted mark' will be removed and the {@link Connectable} moved out the transaction control.
 *   If the value of a {@link Primitive} is changed inside a transaction, and then it is changed back to the
 *   previous value inside the same transaction, also the {@link Primitive} will be taken out the transaction
 *   control. 
 * - All uncommitted modifications are visible only to the transaction that perform them. The exception to this
 *   are creation collisions: If inside a transaction it's created a {@link Link} having a source
 *   or target 'marked as deleted' inside other transaction, then the {@link Link} creation will fail. Also,
 *   if inside a transaction it's created an {@link Identifiable} that has been already created by other
 *   transaction, even if not yet committed, the {@link Identifiable} will fail.
 *   The visibility restriction of uncommitted modifications will be supported by this {@link ContainerImpl},
 *   the {@link ConnectableImpl} and the involved {@link Task}s.
 *
 * It's also a {@link Concept}. But {@link Query}s will interpret its connections as
 * if they where from the {@link Concept}s inside it. So, if we search for {@link Concept}s with
 * a certain type of connections, and one of the results is an instance of this class, the real
 * result will be the {@link Concept}s contained in it. But only its immediate children, not those
 * of {@link Container}s inside it ... so this behavior is not recursive.
 * 
 * @version $Revision$
 * @author  Héctor Cirbián Pousa ($Author$)
 */
public class ContainerImpl extends ConceptImpl implements Modifiable {
  private static final long serialVersionUID = 6886155054872809042L;

  /** {@link AtomicInteger#equals(Object)} is the {@link Object#equals(Object)} one, not valid for us. */
  protected static class AtomicIntegerWithEquals extends AtomicInteger {
    private static final long serialVersionUID = -7765459712022549810L;

    public AtomicIntegerWithEquals() {
      super();
    }

    public AtomicIntegerWithEquals(int initialValue) {
      super(initialValue);
    }

    @Override
    public boolean equals(Object obj) {
      if (obj == null)
        return false;
      if (!(obj instanceof AtomicIntegerWithEquals))
        return false;
      return this.get() == ((AtomicIntegerWithEquals)obj).get();
    }
  }

  private static final AtomicIntegerWithEquals ZERO = new AtomicIntegerWithEquals();

  /** Pure {@link ReturnPoint} decorator for waiting porpouses. */
  private class ReturnPointDecorator<T> extends ReturnPointAdapter<T> {
    private ReturnPoint<? super T> decorated;

    public ReturnPointDecorator(ReturnPoint<? super T> decorated) {
      super();
      this.decorated = decorated;
    }

    public ReturnPoint<? super T> getDecorated() {
      return decorated;
    }

    @Override
    public void start(TaskId taskId) {
      this.decorated.start(taskId);
    }

    @Override
    public void finish(TaskId taskId) {
      super.finish(taskId);
      this.decorated.finish(taskId);
    }

    @Override
    public void manage(TaskId taskId, Exception exception) {
      this.decorated.manage(taskId, exception);
    }

    @Override
    public <U extends T> void manage(TaskId taskId, U result) {
      this.decorated.manage(taskId, result);
    }

    @Override
    public <U extends T> void manage(TaskId taskId, Collection<U> results) {
      this.decorated.manage(taskId, results);
    }    
  }

  // Class to mix a IdFragment with Id to avoid extra Maps in 'externalReferences'.
  protected static class LocalIdAndContainerId implements Comparable<LocalIdAndContainerId>, Serializable {
    private static final long serialVersionUID = -7825783869750567807L;

    // Helpers so we can do SortedMap.subMap with keys of this class obtaining all its values for a given localId.
    public static final Id FROM = new Id(Integer.MIN_VALUE);
    public static final Id TO = new Id(Integer.MAX_VALUE);

    private IdFragment localId;
    private Id containerId;

    public LocalIdAndContainerId(IdFragment localId, Id containerId) {
      super();
      this.localId = localId;
      this.containerId = containerId;
    }

    public Id getContainerId() {
      return containerId;
    }

    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((containerId == null) ? 0 : containerId.hashCode());
      result = prime * result + ((localId == null) ? 0 : localId.hashCode());
      return result;
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      LocalIdAndContainerId other = (LocalIdAndContainerId) obj;
      if (containerId == null) {
        if (other.containerId != null)
          return false;
      }
      else if (!containerId.equals(other.containerId))
        return false;
      if (localId == null) {
        if (other.localId != null)
          return false;
      }
      else if (!localId.equals(other.localId))
        return false;
      return true;
    }

    // Implemented so we can use the FROM/TO limits to get SortedMap.subMap of all elements with same localId.
    @Override
    public int compareTo(LocalIdAndContainerId rhs) {
      int result = this.localId.compareTo(rhs.localId);
      if (result != 0)
        return result;
      if ((this.containerId == rhs.containerId) || (this.containerId.equals(rhs.containerId)))
        return 0;
      if ((this.containerId == FROM) || (rhs.containerId == TO))
        return -1;
      if ((this.containerId == TO) || (rhs.containerId == FROM))
        return 1;
      return this.containerId.compareTo(rhs.containerId);
    }

    @Override
    public String toString() {
      return "LocalIdAndContainerId [containerId=" + containerId + ", localId=" + localId + "]";
    }
  }

  /** Map with all the {@link Identifiable} inside <code>this</code>. */
  protected ConcurrentMap<IdFragment,Identifiable> identifiables;
  /** Allows to reach {@link Link}s inside <code>this</code> by the <code>Id</code> they aim. */
  protected ConcurrentMap<Id, Set<LinkImpl>> reverseLinkReferences;
  // We use the next construction as a simplification of: private ConcurrentMap<IdFragment, ConcurrentMap<Id,AtomicIntegerWithEquals>> externalReferences;
  /** Map with the {@link Container}s {@link Id}s that have {@link Link}s aiming elements belonging to <code>this</code>. */
  protected ConcurrentNavigableMap<LocalIdAndContainerId, AtomicIntegerWithEquals> externalReferences;
  /** Map of elements modified by a transaction. */
  protected ConcurrentMap<TransactionId, Set<ConnectableImpl>> transactionPendingConnectables;
  
//  protected IdFragment idFragment;
//  protected Id         id;  
//  protected Container  container;

  public ContainerImpl(long creationTime, TransactionId transactionId, IdFragment idFragment) {
    super(creationTime, transactionId, idFragment);
    init();
  }

  protected void init() {
    this.identifiables         = new ConcurrentHashMap<IdFragment,Identifiable>();
    this.reverseLinkReferences = new ConcurrentHashMap<Id, Set<LinkImpl>>();
    this.externalReferences    = new ConcurrentSkipListMap<LocalIdAndContainerId, AtomicIntegerWithEquals>();
    this.transactionPendingConnectables = new ConcurrentSkipListMap<TransactionId, Set<ConnectableImpl>>();    
  }

  public ContainerImpl(long creationTime, TransactionId transactionId, String name) {
    this(creationTime, transactionId, new StringIdFragment(name));
  }

  public IdFragment getLocalIdFragment() {
    return this.idFragment;
  }

  public Id getId() {
    if (this.id == null) {
      if (this.container != null)
        this.id = new Id(this.container.getId(), this.idFragment);
      else
        this.id = new Id(this.idFragment);
    }
    return this.id;
  }
    
  public void setContainer(Container container) throws IllegalArgumentException {
    if (this.container != null)
      throw new IllegalArgumentException("Method called more than once.");
    this.container = container;
  }
 
  protected void addReverseLinkReference(LinkImpl link, Id target) {
    if (!Id.RAW_ROOT.isAncestorOf(target)) {
      Set<LinkImpl> aux = null;
      do {
        Set<LinkImpl> references = reverseLinkReferences.get(target);
        if (references == null)
          references = reverseLinkReferences.putIfAbsent(target, aux = new ConcurrentSkipListSet<LinkImpl>());
        if (references == null)
          references = aux;
        references.add(link);
      } // It is possible a concurrent thread removed the 'references' Set. If so, we retry.
      while (((aux = reverseLinkReferences.get(target)) == null) || !aux.contains(link));
    }
  }

  protected void addToTransaction(TransactionId transactionId, ConnectableImpl connectable) {
    Set<ConnectableImpl> conns = this.transactionPendingConnectables.get(transactionId);
    if (conns == null) {
      Set<ConnectableImpl> prev = this.transactionPendingConnectables.putIfAbsent(transactionId, conns = Collections.synchronizedSet(new HashSet<ConnectableImpl>()));
      if (prev != null)
        conns = prev;
    }
    conns.add(connectable);
  }

//  private void removeFromTransaction(TransactionId transactionId, ConnectableImpl connectable) throws TransactionException {
//    Set<ConnectableImpl> conns = this.transactionPendingConnectables.get(transactionId);
//    if (conns == null)
//      throw new TransactionException("Not existent transaction: " + transactionId);
//    if (!conns.remove(connectable))
//      throw new TransactionException("Connectable " + connectable + " not hold by " + transactionId);
//  }

  @Override
  public boolean contains(IdFragment id) {
    return identifiables.containsKey(id);
  }

  @Override
  public boolean contains(Connectable arg) {
    if (arg instanceof Link){ // This container can contain only Identifiables or Link.
      do {
        arg = ((Link)arg).getSource();
      }
      while (arg instanceof Link);
    }
    if (arg instanceof Identifiable) {
      Id id = ((Identifiable)arg).getId();
      return this.getId().isParentOf(id) && this.contains(id.getLastIdFragment());
    }
    else
      return false;
  }

  @Override
  public Concept getConcept(IdFragment id) {
    Identifiable aux = identifiables.get(id);
    if (aux instanceof Concept)
      return (Concept)aux;
    return null;
  }

  @Override
  public List<Concept> getConcepts() {
    Collection<Identifiable> childs = this.identifiables.values();
    List<Concept> results = new ArrayList<Concept>(childs.size());
    for (Identifiable child: childs)
      if ((child instanceof Concept) && !(child instanceof Container))
        results.add((Concept)child);
    return results;
  }

  @Override
  public Set<IdFragment> getLocalIds() {
    return new HashSet<IdFragment>(identifiables.keySet());
  }

  @Override
  public int size() {
    return identifiables.size();
  }

  @Override
  public Set<Link> getLinksReferring(Id target) {
    Set<LinkImpl> aux = this.reverseLinkReferences.get(target);
    if (aux != null)
      return new HashSet<Link>(aux);
    return null;
  }

  @Override
  public Map<Id, Integer> getNumberOfLinksTo(Id root) {
    TreeMap<Id, Integer> result = null;
    for (Map.Entry<Id, Set<LinkImpl>> entry: this.reverseLinkReferences.entrySet()) {
      if (root.isAncestorOf(entry.getKey())) {
        if (result == null)
          result = new TreeMap<Id, Integer>();
        result.put(entry.getKey(), entry.getValue().size());
      }
    }
    if (result != null)
      return result;
    else
      return Collections.emptyMap();
  }

  @Override
  public Set<Id> getContainersReferring(IdFragment target) {
    LocalIdAndContainerId from = new LocalIdAndContainerId(target, LocalIdAndContainerId.FROM);
    LocalIdAndContainerId to   = new LocalIdAndContainerId(target, LocalIdAndContainerId.TO);
    Map<LocalIdAndContainerId,?> subMap = this.externalReferences.subMap(from, to);
    if (subMap == null)
      return null;
    Set<LocalIdAndContainerId> keys = subMap.keySet();
    if (keys == null)
      return null;      
    Set<Id> result = new HashSet<Id>((int)(keys.size()*1.21));
    for (LocalIdAndContainerId mix: keys)
      result.add(mix.getContainerId());
    return result;
  }

  private void removeExternalReferencesTo(IdFragment target) {
    LocalIdAndContainerId from = new LocalIdAndContainerId(target, LocalIdAndContainerId.FROM);
    LocalIdAndContainerId to   = new LocalIdAndContainerId(target, LocalIdAndContainerId.TO);
    Iterator<?> iterator = this.externalReferences.subMap(from, to).entrySet().iterator();
    while (iterator.hasNext()) {
      iterator.next();
      iterator.remove();
    }
  }

  protected <T extends ConceptImpl> T insertConceptImpl(TransactionId transactionId, IdFragment idf, T concept) throws DuplicateElementException {
    if (this.identifiables.putIfAbsent(idf, concept) != null) {
      // XXX: Check if it is feasible to set as UNTOUCHED and out the transaction the existent Concept if is ERASED and hold by this transaction.
      throw new DuplicateElementException("Trying to create in " + this.getId() + " a Concept with an Id allready in use: " + idf);
    }
    concept.setContainer(this);
    if (transactionId != null)
      addToTransaction(transactionId, concept);
    return concept;
  }

  @Override
  public Concept createConcept(long creationTime, TransactionId transactionId, IdFragment idf) throws DuplicateElementException {
    return insertConceptImpl(transactionId, idf, new ConceptImpl(creationTime, transactionId, idf));
  }

  @Override
  public Primitive createPrimitive(long creationTime, TransactionId transactionId, IdFragment idf, Object value) throws DuplicateElementException {
    return insertConceptImpl(transactionId, idf, new PrimitiveImpl(creationTime, transactionId, idf,value));
  }

  @Override
  public Action createAction(long creationTime, TransactionId transactionId, IdFragment idf, String plugin, String implementation) throws DuplicateElementException {
    return insertConceptImpl(transactionId, idf, new ActionImpl(creationTime, transactionId, idf, plugin, implementation));
  }

  @Override
  public Attribute createAttribute(long creationTime, TransactionId transactionId, IdFragment idf, String generatorClass, String generatorMethod) throws DuplicateElementException {
    return insertConceptImpl(transactionId, idf, new AttributeImpl(creationTime, transactionId, idf, generatorClass, generatorMethod));
  }

  @Override
  public void incorporateContainer(Container container) throws DuplicateElementException, WrongStateException {
    if (container.size() > 0)
      throw new WrongStateException("The incorporating Container is not empty.");
    if (this.identifiables.putIfAbsent(container.getLocalIdFragment(), container) != null)
      throw new DuplicateElementException("Trying to create a ContainerImpl with an Id allready in use: " + container.getLocalIdFragment());
    container.setContainer(this);
  }

  protected LinkImpl onlyCreateLinkImpl(long creationTime, TransactionId transactionId, Flow flow, ConnectableImpl source, Id target) throws DuplicateElementException {
    LinkImpl link = new LinkImpl(creationTime, transactionId, flow);
    link.setTarget(target);
    connect(source, link);
    addToTransaction(transactionId, link);
    return link;
  }

  protected Link createLinkImpl(long creationTime, TransactionId transactionId, Flow flow, ConnectableImpl source, Id target) throws TransactionException, DuplicateElementException { 
    if (!source.transactionId.compareAndSet(null, transactionId) && !source.isModifiableBy(transactionId))
      throw new TransactionException("Trying to connect with a Connectable (" + source + " hold by " + source.transactionId.get() + ") not modifiable by " + transactionId);
    source.status.compareAndSet(Status.UNTOUCHED, Status.MODIFIED); 
    addToTransaction(transactionId, source);
    LinkImpl link = this.onlyCreateLinkImpl(creationTime, transactionId, flow, source, target);
    addReverseLinkReference(link, target);
    return link;
  }

  protected void connect(ConnectableImpl source, LinkImpl link) throws DuplicateElementException {
    if (!source.connect(link))
      // XXX: Check if it is feasible to set as UNTOUCHED and out the transaction the existent Link if ERASED and hold by this transaction.
      throw new DuplicateElementException("Trying to create a Link that all ready exists with: " + link.getTarget());
    link.setSource(source);
  }

  protected void cleanSource(LinkImpl link) {
    link.setSource(null);
  }

  @Override
  public Link createLink(long creationTime, TransactionId transactionId, Link.Flow flow, Id target, IdFragment sourceStartPoint, Id ... sourceNextPoints) throws MissingElementException, DuplicateElementException, TransactionException {
    if ((this.getId().isParentOf(target)) && (!this.contains(target.getLastIdFragment())))
      throw new MissingElementException("Trying to connect to an element that does not exists: " + target);
    ConnectableImpl source = findConnectable(sourceStartPoint, sourceNextPoints);
    return createLinkImpl(creationTime, transactionId, flow, source, target);
  }

  @Override
  public Link createLink(long creationTime, TransactionId transactionId, Flow flow, Connectable source, Id target) throws WrongStateException, DuplicateElementException, TransactionException {
    if (!this.contains(source))
      throw new WrongStateException("The " + source + " does not belong to this Container: " + this);
    // else, source belongs to this, and is a ConnectableImpl.
    return createLinkImpl(creationTime, transactionId, flow, (ConnectableImpl)source, target);
  }

  protected ConnectableImpl findConnectable(IdFragment sourceStartPoint, Id ... sourceNextPoints) throws MissingElementException {
    if (sourceStartPoint == null)
      throw new MissingElementException("Trying to find a source using a null IdFragment");
    Identifiable aux = this.identifiables.get(sourceStartPoint);
    if (aux == null)
      throw new MissingElementException("Element '" + sourceStartPoint + "' does not exists in " + this.getId());
    if (!(aux instanceof Connectable))
      throw new MissingElementException("Trying to find a source that is not a Connectable: " + sourceStartPoint);
    ConnectableImpl source = (ConnectableImpl)aux;
    // If sourceNextPoints != null, the Link source is an other Link with its root in source.
    if (sourceNextPoints != null) {
      for (Id id: sourceNextPoints) {
        source = (ConnectableImpl)source.getLink(id);
        if (source == null)
          throw new MissingElementException("Trying to find source that does not exists: " + sourceStartPoint + " " + Arrays.toString(sourceNextPoints));
      }
    }
    return source;
  }

  @Override
  public void trackExternalLinks(IdFragment targetConcept, Id externalContainer, int number) throws MissingElementException {
    if (!this.contains(targetConcept))
      throw new MissingElementException("Trying to connect to an element that does not exists: " + this.getId() + "." + targetConcept + ". Referenced from: " + externalContainer);
    LocalIdAndContainerId mixId = new LocalIdAndContainerId(targetConcept, externalContainer);
    AtomicIntegerWithEquals counter;
    AtomicIntegerWithEquals prev = null;
    do {
      if (prev == null)
        counter = this.externalReferences.get(mixId);
      else
        counter = prev;
      if (counter == null) {
        prev = this.externalReferences.putIfAbsent(mixId, counter = new AtomicIntegerWithEquals(number));
        if (prev != null) {
          counter = prev;
          counter.addAndGet(number);
        }
      }
      else
        counter.addAndGet(number);
    } // It is possible a concurrent thread removed the 'counter'. If so, we retry.
    while ((prev = this.externalReferences.get(mixId)) != counter);
  }

  @Override
  public Primitive setPrimitiveValue(long modificationTime, TransactionId transactionId, IdFragment idf, Object value) throws MissingElementException, WrongStateException, TransactionException {
    Identifiable identifiable = this.identifiables.get(idf);
    if ((identifiable == null) || !(identifiable instanceof Primitive))
      throw new MissingElementException("The primitive does not belong to this container: " + idf);
    PrimitiveImpl primitive = ((PrimitiveImpl)identifiable);
    if (!primitive.transactionId.compareAndSet(null, transactionId) && !transactionId.equals(primitive.transactionId.get()))
      throw new TransactionException("Trying to modifiy a primitive (" + primitive + " hold by " + primitive.transactionId.get() + ") not modifiable by " + transactionId);
    addToTransaction(transactionId, primitive);
    if (!primitive.status.compareAndSet(Status.UNTOUCHED, Status.MODIFIED) && (primitive.status.get() == Status.ERASED))
      throw new TransactionException("Trying to modifiy a primitive (" + primitive + " hold by " + primitive.transactionId.get() + ") not modifiable by " + transactionId);
    primitive.setValue(modificationTime, value);
    return primitive;
  }

  protected void setPrimitiveValue(long modificationTime, PrimitiveImpl primitive, Object value) throws WrongStateException {
    primitive.setValue(modificationTime, value);
    primitive.commit();
  }

  protected void markAsDeleted(TransactionId transactionId, ConnectableImpl connectable) throws TransactionException {
    if (!connectable.transactionId.compareAndSet(null, transactionId) && !transactionId.equals(connectable.transactionId.get()))
      throw new TransactionException("Trying to erase a Connectable (" + connectable + " hold by " + connectable.transactionId.get() + ") not erasable by " + transactionId);
    // XXX: Check if it is feasible to do full delete of a CREATED Connectable hold by this transaction.
    if (!connectable.status.compareAndSet(Status.UNTOUCHED, Status.ERASED) && (connectable.status.get() != Status.ERASED))
      throw new TransactionException("Trying to erase a Connectable (" + connectable + " hold by " + connectable.transactionId.get() + ") not erasable by " + transactionId);
    for(LinkImpl link: connectable.getLinksTable().values())
      markAsDeleted(transactionId, link);
    addToTransaction(transactionId, connectable);
  }

  private void deleteConnectable(List<Id> noMorePointed, TransactionId transactionId, ConnectableImpl connectable, boolean checkErased) throws TransactionException, MissingElementException {
    if (!transactionId.equals(connectable.transactionId.get()) || (checkErased && !Status.ERASED.equals(connectable.status.get())))
      throw new TransactionException("Trying to delete a Connectable (" + connectable + ": " + connectable.status.get() + ") that can't be deleted by " + transactionId);
    for(LinkImpl link: connectable.getLinksTable().values())
      deleteConnectable(noMorePointed, transactionId, link, checkErased);
    if (connectable instanceof LinkImpl) {
      LinkImpl link = (LinkImpl)connectable;
      if (link.getSource() != null) { // Is not deleted yet.
        this.reverseLinkReferences.get(link.getTarget()).remove(link);
        // We remove the Set if it is empty ... through a thread safe construction.
        this.reverseLinkReferences.remove(link.getTarget(), Collections.emptySet());
        if (!this.getId().isParentOf(link.getTarget()))
          noMorePointed.add(link.getTarget());
        ((ConnectableImpl)link.getSource()).disconnect(link);
        link.setSource(null);
        link.setTarget(null);
      }
    }
    else if (connectable instanceof Identifiable) {
      Identifiable identifiable = (Identifiable)connectable;
      if (!this.identifiables.remove(identifiable.getId().getLastIdFragment(), identifiable))
        throw new MissingElementException("The connectable does not belong to this container: " + identifiable.getId());
      // commit and rollback are the only methods that call this one, so external references should be removed. Pending untracks will not fail.  
      removeExternalReferencesTo(identifiable.getId().getLastIdFragment());
    }
    // commit and rollback, the only methods that call this one, remove from the transaction at the beginning. 
    // removeFromTransaction(transactionId, connectable);
  }

  @Override
  public void deleteElement(TransactionId transactionId, Set<Id> insideLinksErased, IdFragment startPoint, Id ... nextPoints) throws MissingElementException, NonDeletableElementException, TransactionException {
    ConnectableImpl connectable = findConnectable(startPoint, nextPoints);
    if (connectable == null)
      throw new MissingElementException("The connectable does not belong to this container: " + startPoint + " " + nextPoints);
    if (connectable instanceof Identifiable) {
      // Links pointing to this Identifiable must be hold by 'trandactionId' and in ERASED status.
      Id erasedId = ((Identifiable)connectable).getId();
      Set<LinkImpl> refs = this.reverseLinkReferences.get(erasedId);
      if (refs != null)
        for (LinkImpl link: this.reverseLinkReferences.get(erasedId))
          if (!link.isModifiableBy(transactionId))
            throw new NonDeletableElementException("Trying to delete an Identifiable still pointed by some Links: " + erasedId);
      insideLinksErased.addAll(this.getContainersReferring(erasedId.getLastIdFragment()));
    }
    markAsDeleted(transactionId, connectable);
  }

  @Override
  public void untrackExternalLinks(IdFragment targetConcept, Id externalContainer, int number) throws MissingElementException {
    // Maybe, the concept has been already removed because here the 'commit' has been done before. 
    // if (!this.identifiables.containsKey(targetConcept))
    //   System.out.println(this.id + " does not contain " + targetConcept);
    LocalIdAndContainerId mixId = new LocalIdAndContainerId(targetConcept, externalContainer);
    AtomicIntegerWithEquals counter = this.externalReferences.get(mixId);
    if (counter == null)
      return; // because te concept has been already removed, no further processing is needed. Nor to throw an exception. throw new MissingElementException("Internal error untracking an external link: " + targetConcept);
    if (counter.addAndGet(-number) == 0)
      this.externalReferences.remove(mixId, ZERO);
  }

  // This method will be used only when this {@link Container} is connected with other one.
  // No other 'tracking events' from that other {@link Container} should be active. So at the same time
  // there can't be executions of this method and of {@link Container#trackExternalLink(IdFragment, Id)}
  // and {@link Container#untrackExternalLink(IdFragment, Id)} for the same 'externalContainer'.
  @Override
  public void setTrackedExternalLinks(IdFragment targetConcept, Id externalContainer, int references) throws MissingElementException {
    if (!this.contains(targetConcept))
      throw new MissingElementException("Trying to connect to an element that does not exists: " + this.getId() + "." + targetConcept + ". Referenced from: " + externalContainer);
    LocalIdAndContainerId mixId = new LocalIdAndContainerId(targetConcept, externalContainer);
    if (references == 0)
      this.externalReferences.remove(mixId);
    else {
      this.externalReferences.put(mixId, new AtomicIntegerWithEquals(references));
    }
  }

  // This method will be used only when this {@link Container} is connected with other one.
  // No other 'tracking events' from that other {@link Container} should be active. So at the same time
  // there can't be executions of this method and of {@link Container#trackExternalLink(IdFragment, Id)}
  // and {@link Container#untrackExternalLink(IdFragment, Id)} for the same 'externalContainer'.
  @Override
  public void untrackExternalLinks(Id root) {
    Iterator<Map.Entry<LocalIdAndContainerId, AtomicIntegerWithEquals>> iter = this.externalReferences.entrySet().iterator();
    while (iter.hasNext()) {
      if (root.sameRoot(iter.next().getKey().getContainerId()))
        iter.remove();
    }
  }

  @Override
  public <R> void route(Task<R> task, ReturnPoint<? super R> rp) {
    this.route(task, this.getId().commonAncestors(task.getTargetContainerId()), rp);
  }

  @Override
  public <R> void route(Task<R> task, int commonAncestors, ReturnPoint<? super R> rp) {
    assert this.getId().size()                >= commonAncestors;
    assert task.getTargetContainerId().size() >= commonAncestors;
    try {
      if (this.getId().size() == commonAncestors) {
        if (task.getTargetContainerId().size() == commonAncestors) {
          assert this.getId().equals(task.getTargetContainerId());
          this.process(task, rp);
        }
        else
          routeToNext(task, commonAncestors, rp);
      }
      else
        routeToPrev(task, commonAncestors, rp);
    }
    catch (ScireException e) {
      rp.manage(task.getTaskId(), e);
      rp.finish(task.getTaskId());
    }
  }

  protected <R> void routeToNext(Task<R> task, int commonAncestors, ReturnPoint<? super R> rp) throws ScireException {
    Identifiable aux = this.identifiables.get(task.getTargetContainerId().getIdFragment(commonAncestors));
    if ((aux == null) || !(aux instanceof Container))
      throw new MissingElementException("Trying to process a Task with an element that does not exists. Target Container: " + task.getTargetContainerId());
    Container nextContainer = (Container)aux;
    nextContainer.route(task, commonAncestors + 1, rp);
  }

  protected <R> void routeToPrev(Task<R> task, int commonAncestors, ReturnPoint<? super R> rp) throws ScireException {
    this.container.route(task, commonAncestors, rp);
  }

  @Override
  public <R> void process(Task<R> task, ReturnPoint<? super R> rp) {
    assert this.getId().equals(task.getTargetContainerId());
    processImpl(task, rp);
  }

  // XXX: Try to eliminate or reduce the unchecked warnings.
  // XXX: Change to no blocking/reactor processing.
  @SuppressWarnings("unchecked")
  private <I,O,Q,L> void processImpl(Task<O> task, ReturnPoint<? super O> rp) {
    try {
      task.start(this, rp);
      if (task instanceof WithDependencies<?,?>) {
        WithDependencies<I,O> withDeps = (WithDependencies<I,O>)task;
        processSubTasks(withDeps.getDependenciesReturnPoints().iterator(), withDeps.getDependencies());
      }
      task.process(this);
      if (task instanceof WithConsequences<?,?>) {
        WithConsequences<O,Q> withCons = (WithConsequences<O,Q>)task;
        processSubTasks(withCons.getConsequencesReturnPoints().iterator(), withCons.getConsequences());
      }
      if (task instanceof WithSubsequentTasks<?,?>) {
        WithSubsequentTasks<O,L>                    withSubs = (WithSubsequentTasks<O,L>)task;
        List<? extends NeedsPrecedent<? super O,L>> subs     = withSubs.getSubsequentTasks();
        if ((subs != null) && (subs.size() > 0)) {
          Iterator<? extends ReturnPoint<? super L>> reps = withSubs.getSubsequentTasksReturnPoints().iterator();
          Iterator<? extends Collection<O>>          ress = withSubs.getResultsForSubsequentTasks().iterator();
          ReturnPointWaitingObserver                 wo   = new ReturnPointWaitingObserver(subs.size());
          ReturnPointDecorator<L>                    dec  = null;
          for (NeedsPrecedent<? super O,L> sub:subs) {
            ReturnPoint<? super L> rep = reps.next();
            if ((dec == null) || (rep != dec.getDecorated()))
              dec = new ReturnPointDecorator<L>(rep);
            dec.setObserver(wo);
            sub.setPrecedent(task, ress.next());
            this.route(sub, dec);
          }
          wo.await();
        }
      }
      if ((task instanceof Modification<?>) && task.getTaskId().getTransactionId().isAutocommit())
        this.process(new CommitTask(task.getTaskId().next(true), this.getId()), new ReturnPointDecoratorForErrorsHandling<Void>(rp));
    }
    catch (Exception ex) {
      rp.manage(task.getTaskId(), ex);
      if ((task instanceof Modification<?>) && task.getTaskId().getTransactionId().isAutocommit())
        this.process(new RollBackTask(task.getTaskId().next(true), this.getId()), new ReturnPointDecoratorForErrorsHandling<Void>(rp));
    }
    finally {
      task.finish();
    }
  }

  private <I> void processSubTasks(Iterator<? extends ReturnPoint<? super I>> reps, List<? extends Task<I>> subs) throws InterruptedException {
    if ((subs != null) && (subs.size() > 0)) {
      ReturnPointWaitingObserver wo  = new ReturnPointWaitingObserver(subs.size());
      ReturnPointDecorator<I>    dec = null;
      for (Task<I> dep: subs) {
        ReturnPoint<? super I> rep = reps.next();
        if ((dec == null) || (rep != dec.getDecorated()))
          dec = new ReturnPointDecorator<I>(rep);
        dec.setObserver(wo);
        this.route(dep, dec);
      }
      wo.await();
    }
  }

  private void checkErased(TransactionId transactionId, ConnectableImpl connectable) throws TransactionException {
    if (!transactionId.equals(connectable.transactionId.get()))
      throw new TransactionException("Transaction corruption. Transaction " + transactionId + " accessing element of transaction " + connectable.transactionId);
    for (LinkImpl link:connectable.getLinksTable().values()) {
      if (link.status.get() != Status.ERASED)
        throw new TransactionException("Expected to be erased connectable has not such state: " + link);
      checkErased(transactionId, link);
    }
  }

  @Override
  public Map<Id,Set<Id>> checkCommit(TransactionId transactionId) throws TransactionException {
    Map<Id,Set<Id>> externalReferences = new HashMap<Id, Set<Id>>();
    Set<ConnectableImpl> connectables = this.transactionPendingConnectables.get(transactionId);
    if (connectables == null)
      return null; // This transaction changed nothing.
    for (ConnectableImpl connectable: connectables) {
      switch (connectable.status.get()) {
        case ERASED:
          if (connectable instanceof Identifiable) {
            Id id = ((Identifiable)connectable).getId();
            Set<LinkImpl> references = this.reverseLinkReferences.get(id);
            if (references != null)
              for (LinkImpl link:references)
                checkErased(transactionId, link);
            Set<Id> externalContainers = this.getContainersReferring(id.getLastIdFragment());
            if(externalContainers != null)
              externalReferences.put(id, externalContainers);
          }
          checkErased(transactionId, connectable);
          break;
        case CREATED:
        case MODIFIED:
        default:
          if (!transactionId.equals(connectable.transactionId.get()))
            throw new TransactionException("Transaction corruption. Transaction " + transactionId + " accessing " + connectable);
      }
    }
    return externalReferences;
  }

  @Override 
  public List<Id> commit(TransactionId transactionId) throws TransactionException, MissingElementException {
    Set<ConnectableImpl> connectables = this.transactionPendingConnectables.remove(transactionId);
    if (connectables == null)
      return null; // This transaction changed nothing.
    List<Id> noMorePointed = new LinkedList<Id>();
    for (ConnectableImpl connectable: connectables) {
      if (!transactionId.equals(connectable.transactionId.get()))
        throw new TransactionException("Transaction corruption. Transaction " + transactionId + " accessing element of transaction " + connectable.transactionId);
      switch (connectable.status.get()) {
        case ERASED:
          deleteConnectable(noMorePointed, transactionId, connectable, true);
          connectable.transactionId.set(null);
          break;
        case CREATED:
        case MODIFIED:
          if (connectable instanceof PrimitiveImpl)
            ((PrimitiveImpl)connectable).commit();
          connectable.status.set(Status.UNTOUCHED);
        default:
          connectable.transactionId.set(null);
      }
    }
    return noMorePointed;
  }
 
  @Override
  public List<Id> rollback(TransactionId transactionId) throws TransactionException, MissingElementException {
    Set<ConnectableImpl> connectables = this.transactionPendingConnectables.remove(transactionId);   
    if (connectables == null)
      return null; // This transaction changed nothing, so there is nothing to roll back.
    List<Id> noMorePointed = new LinkedList<Id>();
    for (ConnectableImpl connectable: connectables) {
      if (!transactionId.equals(connectable.transactionId.get()))
        throw new TransactionException("Transaction corruption. Rollback of " + transactionId + " accessing element of transaction " + connectable.transactionId);
      switch (connectable.status.get()) {
        case CREATED:
          deleteConnectable(noMorePointed, transactionId, connectable, false);
          connectable.transactionId.set(null);
          break;
        case MODIFIED:
        case ERASED:
          if (connectable instanceof PrimitiveImpl)
            ((PrimitiveImpl)connectable).rollback();
          connectable.status.set(Status.UNTOUCHED);
        default:
          connectable.transactionId.set(null);
      }
    }
    return noMorePointed;
  }

  @Override
  public Set<TransactionId> getPendingTransactions() {
    return this.transactionPendingConnectables.keySet();
  }
}